#include "GLWidget.h"
#include "../Core/Exceptions.h"
#include "../Core/Materials.h"

#include "../Core/Constants.h"
#include "../Core/HCoordinates3.h"


#include "../Core/LinearCombination3.h"
#include "../Parametric/Circle.h"
#include "../Parametric/TestCurves.h"


#include <fstream>
using namespace std;

namespace cagd
{
    // default/special constructor
    GLWidget::GLWidget(QWidget *parent, const QGLFormat &format): QGLWidget(format, parent)
    {
        timer = new QTimer(this);
        timer->setInterval(0);

        connect(timer, SIGNAL(timeout()), this, SLOT(animate()));
    }

    // destructor
    GLWidget::~GLWidget()
    {
        // free every dynamically allocated object
        if (_cc)
        {
            delete _cc, _cc = 0;
        }

        if (_image_cc)
        {
            delete _image_cc, _image_cc = 0;
        }

        if (_icc)
        {
            delete _icc, _icc = 0;
        }

        if (_image_icc)
        {
            delete _image_icc, _image_icc = 0;
        }

        if (_tbs)
        {
            delete _tbs, _tbs = 0;
        }

        if (_image_tbs)
        {
            delete _image_tbs, _image_tbs = 0;
        }

        if (_itbs)
        {
            delete _itbs, _itbs = 0;
        }

        if (_image_itbs)
        {
            delete _image_itbs, _image_itbs = 0;
        }

        if (_trigo_population)
        {
            delete _trigo_population, _trigo_population = 0;
        }
        if (timer) {
            delete timer, timer = 0;
        }
    }

    // initializer method
    // it is called once before the first call to paintGL() or resizeGL(),
    // and then once whenever the widget has been assigned a new QGLContext
    void GLWidget::initializeGL()
    {
        glewInit();

        // define a projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)width() / (GLfloat)height();
        _fovy = 45.0;
        _z_near = 1.0;
        _z_far = 1000.0;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // define a model view matrix
        glMatrixMode(GL_MODELVIEW);

        _eye[2] = 6.0;
        _up[1] = 1.0;

        glLoadIdentity();
        gluLookAt(_eye[0], _eye[1], _eye[2], _center[0], _center[1], _center[2], _up[0], _up[1], _up[2]);

        // enable the required OpenGL states

        // enable depth cing
        glEnable(GL_DEPTH_TEST);

        // ensure unit normal vectors
        glEnable(GL_NORMALIZE);

        // enable default lighting
        glEnable(GL_LIGHTING);

        // enable default light source 0, which is a directional light with grey ambient, diffuse and specular intensities
        glEnable(GL_LIGHT0);

        // set the background color
        glClearColor(0.0, 0.0, 0.0, 1.0);
//        glClearColor(1.0, 1.0, 1.0, 1.0);

        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 1.0;
        _angle_x = _angle_y = _angle_z = 0.0;

        _cc = 0;
        _image_cc = 0;
        _icc = 0;
        _image_icc = 0;

        _tbs = 0;
        _itbs = 0;
        _image_tbs = 0;
        _image_itbs = 0;
        _trigo_population = 0;

        timer = 0;

        circle::radius = 1.0;
//        radius = 1;

        GLuint max_order_of_derivatives = 2;

        ColumnMatrix<ParametricCurve3::Derivative> derivatives;
        derivatives.ResizeRows(max_order_of_derivatives + 1);

//        derivatives(0) = circle::d0;
//        derivatives(1) = circle::d1;
//        derivatives(2) = circle::d2;
        derivatives(0) = Cornu::d0;
        derivatives(1) = Cornu::d1;
        derivatives(2) = Cornu::d2;

        pCurve = new ParametricCurve3(Cornu::u_min, Cornu::u_max, GL_FALSE, derivatives);

        if (!pCurve)
        {
            throw Exception("Could not create cyclic curve!");
        }
        else {
            GLuint div_point_count = 200;
            pCurve_image = 0;
            pCurve_image = pCurve->GenerateImage(div_point_count);

            if (!pCurve_image) {
                throw Exception("Could not create the image of the cyclic curve!");
            } else {
                if (!pCurve_image->UpdateVertexBufferObjects()) {
                    throw Exception("Could not create the vertex buffer object of the image!");
                }
            }
        }



    }

    void GLWidget::evolve(int checked) {
        min_length = -1;
        if (checked > 0) {
//            timer->setInterval(5000);
            timer->start();
        } else {
            timer->stop();
        }
    }

    void GLWidget::animate() {
        _trigo_population->evolve();

        if (best && best->getImage())
            updateGL();
    }

    // rendering method
    void GLWidget::paintGL()
    {
        // clear the color and depth buffers
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // save (or duplicate) the modelview matrix
        glPushMatrix();

            // perform your transformations and render your geometry
            glRotated(_angle_x, 1.0, 0.0, 0.0);
            glRotated(_angle_y, 0.0, 1.0, 0.0);
            glRotated(_angle_z, 0.0, 0.0, 1.0);

            glTranslated(_trans_x, _trans_y, _trans_z);

            glScaled(_scale, _scale, _scale);

            if (pCurve_image) {
                glDisable(GL_LIGHTING);
                glColor3f(1.0, 0.0, 0.0);
                glLineWidth(2.0);
                pCurve_image->RenderDerivatives(0, GL_LINE_STRIP);
                glLineWidth(1.0);

                glColor3f(1.0, 0.8, 0.0);
                pCurve_image->RenderDerivatives(1, GL_LINES);

                glColor3f(0.1, 0.5, 1.0);
                pCurve_image->RenderDerivatives(2, GL_LINES);
                glEnable(GL_LIGHTING);
            }


        // restore the original modelview matrix
        glPopMatrix();
    }

    // resizing method
    void GLWidget::resizeGL(int w, int h)
    {
        // set the viewport
        glViewport(0, 0, w, h);

        // define the projection matrix
        glMatrixMode(GL_PROJECTION);

        _aspect = (GLfloat)w / (GLfloat)h;

        glLoadIdentity();
        gluPerspective(_fovy, _aspect, _z_near, _z_far);

        // revert to the modelview matrix
        glMatrixMode(GL_MODELVIEW);

        // update your scene, i.e., invoke indirectly the method paintGL()
        updateGL();

    }

    // implementation of public event handlers
    void GLWidget::set_angle_x(int value)
    {
        if (_angle_x != (double)value)
        {
            _angle_x = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_y(int value)
    {
        if (_angle_y != (double)value)
        {
            _angle_y = value;
            updateGL();
        }
    }

    void GLWidget::set_angle_z(int value)
    {
        if (_angle_z != (double)value)
        {
            _angle_z = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_x(double value)
    {
        if (_trans_x != value)
        {
            _trans_x = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_y(double value)
    {
        if (_trans_y != value)
        {
            _trans_y = value;
            updateGL();
        }
    }

    void GLWidget::set_trans_z(double value)
    {
        if (_trans_z != value)
        {
            _trans_z = value;
            updateGL();
        }
    }

    void GLWidget::set_zoom_factor(double value)
    {
        if (_scale != value)
        {
            _scale = value;
            updateGL();
        }
    }

    void GLWidget::reset_to_original_view()
    {
        _trans_x = _trans_y = _trans_z = 0.0;
        _scale = 1.0;
        _angle_x = _angle_y = _angle_z = 0.0;

        updateGL();
    }
}
